<script lang="ts" setup>
import type {PostAction} from "../../network/api/PostApi.ts";
import {onMounted, ref} from "vue";
import {useAccountStore} from "../../store/AccountStore.ts";
import type {PrepareBody, UploadBody} from "../../network/types/Post.ts";
import PostTextArea, {type CompletionOption} from "./PostTextArea.vue";
import {ElMessage, ElMessageBox, ElNotification} from "element-plus";
import {useResourceStore} from "../../store/ResourceStore.ts";
import type {ThirdPartyEmote, ThirdPartyEmoteCategory, ThirdPartyEmoteGroup} from "../../types/Emote.ts";
import type {NgaLocation} from "../../network/types/NgaLocation.ts";
import {attachCode, type Attachment} from "../../network/types/Reply.ts";
import PostAttachment from "./PostAttachment.vue";
import type {BbsCodeBody, Emote, EmoteGroup} from "../../network/types/Resources.ts";

const TAG = '[发帖表单]'

export interface PostProps {
  action: PostAction,
  fid?: number | undefined,
  stid?: number | undefined,
  tid?: number | undefined,
  pid?: number | undefined,
  comment?: 1 | undefined,
  simple?: boolean | undefined
}

const props = defineProps<PostProps>()
const emit = defineEmits(["error", "success"])
const resourceStore = useResourceStore();

const blockedNames = resourceStore.blockedNames()
const prepareBody = ref<PrepareBody>()
const fontColor = ref<string[]>()
const emoteGroups = ref<EmoteGroup[]>()
const thirdPartyEmoteGroups = ref<ThirdPartyEmoteGroup[]>()
// 自动完成选项： 颜色、官方表情、第三方表情
const autoCompletionOptions = ref<CompletionOption[]>()

const loading = ref(false)
const showEmoteDialog = ref(false)
const closeEmoteDialogAfterClick = ref(true)
const editor = ref()
// 正在发送回复
let sending = false
const hidden = ref<1 | 2>(2)
const anony = ref(false)
const replyOnce = ref(false)

// 发帖API
const postApi = useAccountStore().getNgaApi().post;
// 本次上传的附件
const uploadBodies = ref<UploadBody[]>([])
// 接收上传的附件后缀
const accept = [".png", ".jpg", ".jpeg", ".gif", ".zip", ".mp3", ".mp4"]

// 向正文中插入文本
const bracket = (prefix = "", suffix = "") => editor.value?.bracket(prefix, suffix)
// 回复准备
const prepare = async () => {
  loading.value = true
  try {
    const res = await postApi.prepare(
        props.action,
        props.fid,
        props.stid,
        props.tid,
        props.pid,
        props.comment
    )
    loading.value = false
    prepareBody.value = res

    hidden.value = res.hidden === 1 ? 1 : 2
    anony.value = false
    replyOnce.value = false

    console.log(TAG, res)
    loadCompletionOption()
  } catch (e: any) {
    // 发生错误，抛出事件
    if (e && e.error) {
      console.error(e)
      const {code, message} = e.error
      if (code === 39) {
        ElMessage.error(`${code}:${message}, 稍后自动重试`)
        setTimeout(() => prepare(), 3000)
      } else {
        emit("error", e.error)
      }
    } else {
      console.error(e)
      setTimeout(() => prepare(), 2000)
    }
  }
}
// 发送回复
const sendPost = async (content: string) => {
  console.log(TAG, "发送回复", content)
  if (sending) {
    ElMessage.warning("正在发送中，请稍候")
    return
  }
  if (props.action === 'new' && !prepareBody.value?.title) {
    ElMessage.warning("标题不能为空")
    return
  }

  sending = true
  try {
    const res = await postApi.sendPost(
        props.action,
        {
          title: prepareBody.value?.title,
          content,
          modifyAppend: prepareBody.value?.modifyAppend,
          hidden: hidden.value,
          anony: anony.value,
          replyOnce: replyOnce.value,
          attachments: uploadBodies.value,
          mention: props.action !== 'modify',
        },
        props.fid,
        props.stid,
        props.tid,
        props.pid,
        props.comment
    ) as NgaLocation

    // 发帖成功
    emit("success", res)
    // const {tid, fid, pid} = res.data

    switch (props.action) {
      case "new": {
        ElMessage.success("发布成功")
        break
      }
      case "modify": {
        ElMessage.success("修改成功")
        break
      }
      default:
        ElMessage.success("回复成功")
    }

  } catch (e) {
    console.error(e)
  }
  sending = false
}
// 转义正则
const escapeRegExp = (string: string) => string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
// 删除已上传附件
const attachDel = (index: number) => {
  const attachments = prepareBody.value?.attachments
  if (!attachments) return
  const attachment = attachments[index]

  ElMessageBox.confirm(`确定要删除此附件吗？ ` + attachment.originalFilename, '提示', {
    type: 'warning'
  }).then(() => {
    // 删除附件
    console.log(TAG, '删除附件', attachment)

    postApi.attachDel(attachment.filename, props.tid!!, props.pid).then(res => {
      ElMessage.success('删除成功')

      for (let thumb of attachment.thumbs) {
        const regExp = escapeRegExp(`[img]./${thumb}[/img]`);
        editor.value?.replace(new RegExp(regExp, 'g'))
      }
      editor.value?.focus()
      attachments?.splice(index, 1)
    })
  })

}
// 放弃本次上传的附件
const attachRemove = (index: number) => {
  const attachment = uploadBodies.value?.[index]
  if (!attachment) return

  ElMessageBox.confirm(`确定要放弃此附件吗？ ` + attachment.file.name, '提示', {
    type: 'warning'
  }).then(() => {
    // 放弃附件
    console.log(TAG, '放弃附件', attachment)
    uploadBodies.value.splice(index, 1)
  })
}
// 点击表情
const clickEmote = (emote: Emote) => {
  if (closeEmoteDialogAfterClick.value) {
    showEmoteDialog.value = false
  }
  bracket(emote.code)
}
// 点击第三方表情
const clickEmoteThirdParty = (emote: ThirdPartyEmote) => {
  if (closeEmoteDialogAfterClick.value) {
    showEmoteDialog.value = false
  }
  bracket(`[img]${emote.img}[/img]`)
}

// 上传一个文件
const uploadFile = (file: File) => postApi.attachUpload("/upload", prepareBody.value?.auth!!, prepareBody.value?.fid!!, file).then(res => {
  const uploadBody = res.data;
  console.log(uploadBody)
  uploadBodies.value.push(uploadBody)
  return uploadBody
})

const onUploadError = (r: any, file: File) => {
  const {status, response, message} = r
  console.error('上传失败', r)
  console.error('上传失败', response)

  if (status) {
    ElNotification({
      title: status.toString(),
      message: `${file.name} --> ${message}`,
      type: "warning",
      duration: 0,
    })
  } else {
    ElNotification({
      title: "上传失败",
      message: `${file.name} --> ${message}`,
      type: "warning",
      duration: 0,
    })
  }


}
// element plus 上传附件
const epUpload = (e: any) => uploadFile(e.file).catch((r: any) => onUploadError(r, e.file))

const onPastedFile = async (files: File[]) => {
  console.log('上传文件', files)
  ElMessage.success(files.length === 1 ? "开始上传" : `${files.length} 个文件将按顺序上传`)
  let thisFile: File
  try {
    for (let file of files) {
      thisFile = file
      const uploadBody = await uploadFile(file)
      // 自动插入code
      const uri = [...uploadBody.thumbs].reverse()[0]
      const code = attachCode(uri, file.name)
      insertAttachCode([code, file.name])
    }

    ElMessage.success(`${files.length} 个文件上传完毕`)
  } catch (r: any) {
    onUploadError(r, thisFile!!)
  }
}

const insertAttachCode = (e: string[]) => {
  const [code, filename] = e
  // console.log(code, filename)
  const pixivId = /(\d+)_p\d+/.exec(filename)?.[1]
  const twitterId = /[^0-9]*(\d{19})[^0-9]*/.exec(filename)?.[1]

  if (pixivId) {
    bracket(`[quote]\npix: ${pixivId}\n${code}\n[/quote]`)
    return
  }
  if (twitterId) {
    bracket(`[quote]\nhttps://x.com/i/status/${twitterId}\n${code}\n[/quote]`)
    return
  }
  bracket(code)
}

const loadCompletionOption = () => {
  resourceStore.bbsCode().then((res: BbsCodeBody) => {
    fontColor.value = res.fontColor
    emoteGroups.value = res.emoteGroups.filter(i => !blockedNames.includes(i.name))

    // 构造自动完成选项： 颜色
    const fontColorOptions: CompletionOption = {
      expr: /\[[\w=]*/,
      options: res.fontColor.map(color => {
        return {
          label: `[color=${color}]`,
          detail: '颜色',
          apply: editor.value?.bbsCodeApply(`[color=${color}]`, "[/color"),
          info: (completion) => {
            const e = document.createElement("span");
            e.innerHTML = "&nbsp;".repeat(10)
            e.style.backgroundColor = /color=(.+)]/.exec(completion.label)?.[1] || ""
            return e;
          }
        }
      })
    }
    // 构造自动完成选项： 官方表情
    const emoteOptions: CompletionOption = {
      expr: /@[^@]*/,
      options: res.emoteGroups.flatMap(group => {
        return group.emotes.map(emote => {
          return {
            label: '@' + emote.name,
            detail: group.name + " - " + emote.name,
            apply: editor.value?.bbsCodeApply(emote.code, ""),
            info: (_) => {
              const e = document.createElement("img");
              e.src = emote.url
              return e;
            }
          }
        })
      })
    }


    autoCompletionOptions.value = autoCompletionOptions.value ? [...autoCompletionOptions.value, fontColorOptions, emoteOptions] : [fontColorOptions, emoteOptions]
  })

  resourceStore.thirdPartyEmoteCategory().then((res: ThirdPartyEmoteCategory[]) => {
    thirdPartyEmoteGroups.value = res.flatMap(i => i.groups).filter(i => !blockedNames.includes(i.name))
    // 构造自动完成选项： 第三方表情
    const options: CompletionOption = {
      expr: /#[^#]*/,
      options: res.flatMap(i => i.groups).flatMap(group => {
        return group.data.map((emote, index) => {
          return {
            label: '#' + group.name + (emote.name || index),
            detail: group.name + " - " + (emote.name || index),
            apply: editor.value?.bbsCodeApply(`[img]${emote.img}[/img]`, ""),
            info: (_) => {
              const e = document.createElement("img");
              e.src = emote.img.replace('./', '/static/attach/attachments/')
              return e;
            }
          }
        })
      })
    }
    autoCompletionOptions.value = autoCompletionOptions.value ? [...autoCompletionOptions.value, options] : [options]
  })
}

onMounted(async () => {
  await prepare()
})

</script>

<template>
  <div v-loading="loading" class="route-root">
    <!--顶部工具栏-->
    <div v-if="!simple" style="text-align: start">
      <!--开关： 匿名，单贴，隐藏-->
      <div>
        <el-switch v-model="hidden" :active-value="1" :inactive-value="2" active-text="隐藏" class="post-switch"/>
        <el-switch v-if="props.action !=='modify'" v-model="anony" active-text="匿名" class="post-switch"/>
        <el-switch v-if="props.action ==='new'" v-model="replyOnce" active-text="单贴" class="post-switch"/>
      </div>
      <!--提示信息-->
      <div v-if="prepareBody?.costInfo">
        <el-text type="danger">{{ prepareBody?.costInfo }}</el-text>
      </div>
      <!--标题-->
      <div v-if="prepareBody">
        <el-input v-model="prepareBody.title" :placeholder="`标题(${action==='new'?'必填':'选填'})`"/>
      </div>
      <el-button-group size="small" type="primary">
        <el-button @click="bracket('[size=100%]', '[/size]')">大小</el-button>
        <el-button @click="bracket('[code]', '[/code]')">代码</el-button>
        <el-button @click="bracket('[collapse=折叠标题]', '[/collapse]')">折叠</el-button>
        <el-button @click="bracket('[b]', '[/b]')">粗体</el-button>
        <el-button @click="bracket('[u]', '[/u]')">下划线</el-button>
        <el-button @click="bracket('[i]', '[/i]')">斜体</el-button>
        <el-button @click="bracket('[h]', '[/h]')">段落标题</el-button>
        <el-button @click="bracket('[quote]', '[/quote]')">引用框</el-button>
        <el-button @click="bracket('[del]', '[/del]')">删除线</el-button>
        <el-button @click="bracket('[list]\n[*]列表项1\n[*]列表项2\n[*]列表项3\n[/list]' )">列表</el-button>
        <el-button @click="bracket('[dice]1d100', '[/dice]')">骰子</el-button>
        <el-button @click="bracket('[url=链接地址]链接标题', '[/url]')">链接</el-button>
        <el-button @click="bracket('[table]\n[tr]\n[td]表格内容[/td]\n[/tr]\n[/table]' )">表格</el-button>
        <el-button @click="showEmoteDialog=true">表情</el-button>
        <el-dropdown effect="light" placement="bottom" trigger="hover">
          <template #dropdown>
            <el-dropdown-menu>
              <el-dropdown-item v-for="(color, index) in fontColor" :key="index"
                                :style="{backgroundColor:color}" @click="bracket(`[color=${color}]`, '[/color]')">{{ color }}
              </el-dropdown-item>
            </el-dropdown-menu>
          </template>
          <el-button>颜色</el-button>
        </el-dropdown>
      </el-button-group>
    </div>
    <!--正文编辑框-->
    <div v-if="prepareBody">
      <post-text-area ref="editor"
                      v-model="prepareBody.content"
                      :auto-completion="autoCompletionOptions"
                      @send-post="sendPost"
                      @pasted-file="onPastedFile"
      />
    </div>
    <!--附件上传和管理-->
    <div v-if="prepareBody" style="padding: 10px">
      <el-tabs>
        <el-tab-pane label="附件上传">
          <div>
            <el-upload
                :accept="accept.join(',')"
                :http-request="epUpload"
                :show-file-list="false"
                multiple
            >
              <el-button type="primary">上传</el-button>
            </el-upload>
          </div>
          <post-attachment
              :data="uploadBodies"
              :filename="(i: UploadBody)=>i.file.name"
              :size="(i: UploadBody)=>`${Math.floor(i.file.size/1024)} KB`"
              :thumbs="(i: UploadBody)=>i.thumbs"
              :uri="(i: UploadBody)=>i.blob"
              @delete="attachRemove"
              @insert="insertAttachCode($event)"
          />
        </el-tab-pane>
        <el-tab-pane v-if="action==='modify' && prepareBody.attachments" label="附件管理">
          <post-attachment
              :data="prepareBody.attachments"
              :description="(i: Attachment)=>i.description"
              :filename="(i: Attachment)=>i.originalFilename"
              :size="(i: Attachment)=>`${i.size} KB`"
              :thumbs="(i: Attachment)=>i.thumbs"
              :uri="(i: Attachment)=>'./'+i.thumbs[0]"
              @delete="attachDel"
              @insert="insertAttachCode($event)"/>
        </el-tab-pane>
      </el-tabs>
    </div>
    <!--表情包选择框-->
    <div>
      <el-dialog v-model="showEmoteDialog" class="bg-color">
        <div>
          <el-switch v-model="closeEmoteDialogAfterClick" active-text="点击后关闭"/>
        </div>
        <div>
          <el-tabs>
            <el-tab-pane v-for="group in emoteGroups" v-if="emoteGroups" :label="group.name" style="text-align: start">
              <!--{{ group.emotes }}-->
              <el-image v-for="(emote, index) in group.emotes"
                        :key="index"
                        :src="emote.url"
                        class="clickable emote-icon"
                        loading="lazy"
                        @click="clickEmote(emote)"
              />
            </el-tab-pane>
            <el-tab-pane v-for="group in thirdPartyEmoteGroups" v-if="thirdPartyEmoteGroups" :label="group.name" style="text-align: start">
              <!--{{ group.emotes }}-->
              <div>作者: <b>{{ group.author }}</b></div>
              <el-scrollbar max-height="500px">
                <el-image v-for="(emote, index) in group.data"
                          :key="index"
                          :src="emote.img.replace('./','/static/attach/attachments/')"
                          class="clickable emote-icon"
                          loading="lazy"
                          @click="clickEmoteThirdParty(emote)"
                />
              </el-scrollbar>
            </el-tab-pane>

          </el-tabs>
        </div>
      </el-dialog>
    </div>
  </div>
</template>

<style scoped>
.emote-icon {
  margin-right: 5px;
  max-width: 120px;
}

.post-switch {
  margin-right: 10px;
}
</style>